/**
 * author: liuxu
 * date: 2014-10-27
 *
 * Use http protocol to request text data. Use a storage cache to hold
 * the requested data.
 *
 * BaseRequest is used to begin the request. Construct BaseRequest with
 * url as param, or use BaseRequest.Builder to add more options.
 * DataHull is used to hold the returned data and status.
 *
 * Demo: com.lx.utils.demo.ActivityRequestWeather
 */

package lx.utils.http.TextRequest;

import java.util.HashMap;
import java.util.concurrent.Executor;

import lx.utils.http.TextRequest.DataHull.DataSource;
import lx.utils.http.TextRequest.DataHull.DataStatus;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;

public class BaseRequest {

    private String mUrl;
    private boolean mIsNeedCache = false;
    private long mCacheTime = Constants.DEFAULT_CACHE_TIME;
    private int mHttpRetryCount = Constants.DEFAULT_HTTP_RETRY_COUNT;
    private long mHttpRetryInterval = Constants.DEFAULT_RETRY_INTERVAL;
    private int mHttpReadTimeOut = Constants.DEFAULT_HTTP_READ_TIMEOUT;
    private int mHttpConnectTimeOut = Constants.DEFAULT_HTTP_CONNECT_TIMEOUT;
    private HashMap<String, String> mHttpHeaders;

    private final Context mContext;
    private DataParser mDataParser;
    private TaskCallback mTaskCallback;

    private class MyAsyncTask extends AsyncTask<String, Void, DataHull> {

        @Override
        protected DataHull doInBackground(String... params) {
            return requestData(params[0]);
        }

        @Override
        protected void onPostExecute(DataHull result) {
            super.onPostExecute(result);
            if (mTaskCallback != null) {
                mTaskCallback.onResponse(result);
            }
        }
    }

    public BaseRequest(Context cxt, String url) {
        mContext = cxt;
        mUrl = url;
    }

    public BaseRequest(
            Context cxt, String url,
            boolean needCache, long cacheTime,
            int httpConnectTimeOut, int httpReadTimeOut,
            int httpRetryCount, long httpRetryInterval,
            HashMap<String, String> httpHeaders,
            DataParser parser, TaskCallback callback) {
        this(cxt, url);
        mIsNeedCache = needCache;
        mCacheTime = cacheTime;
        mHttpReadTimeOut = httpReadTimeOut;
        mHttpConnectTimeOut = httpConnectTimeOut;
        mHttpRetryCount = httpRetryCount;
        mHttpRetryInterval = httpRetryInterval;
        mHttpHeaders = httpHeaders;
        mDataParser = parser;
        mTaskCallback = callback;
    }

    /**
     * run the request in a single thread
     */
    public void execute() {
        MyAsyncTask task = new MyAsyncTask();
        task.execute(mUrl);
    }

    /**
     * run the request in a thread pool
     */
    public void executeOnExecutor(Executor executor) {
        MyAsyncTask task = new MyAsyncTask();
        task.executeOnExecutor(executor, mUrl);
    }

    /**
     * request data. call this method if you want to do the
     * thread coding for yourself. this method will not
     * invoke TaskCallback.onResponse()
     * @return the encapsulated data and status get from the request
     */
    public DataHull requestData() {
        DataHull datahull = requestData(mUrl);
        return datahull;
    }

    private DataHull requestData(String url) {
        DataHull datahull = new DataHull(url);
        if (url == null) {
            datahull.setDataStatusCode(DataStatus.ERR_WRONG_URL);
            return datahull;
        }

        // try cache first
        if (mIsNeedCache && getFromCache(datahull)) {
            return datahull;
        }

        // cache not available, try server
        if (!isNetworkAvailable(mContext)) {
            datahull.setDataStatusCode(DataStatus.ERR_NO_NET_WORK);
            return datahull;
        }

        HttpHandler httpHandler = new HttpHandler(
                getReadTimeOut(), getConnectTimeOut());
        for (int i = 0; i <= getTotalRetryCount(); i++) {
            httpHandler.doGet(datahull, getHeaders());
            if (datahull.getDataStatusCode() == DataStatus.STATUS_OK) {
                break;
            } else {
                try {
                    Thread.sleep(mHttpRetryInterval);
                } catch (InterruptedException e) {
                }
            }
        }

        datahull.setDataSource(DataSource.FROM_SERVER);
        if (mDataParser != null) {
            if (parseDataForDataHull(datahull) && mIsNeedCache) {
                // when a DataParser is provided, we should not
                // cache the request if parse failed
                saveToCache(datahull);
            }
        } else {
            if (mIsNeedCache) {
                saveToCache(datahull);
            }
        }
        return datahull;
    }

    private boolean getFromCache(DataHull datahull) {
        HttpCache cache = new HttpCache(mContext, mCacheTime);
        String data = cache.readCache(datahull.getRequestUrlString());
        if (data != null) {
            datahull.setDataSource(DataSource.FROM_SDCARD_CACHE);
            datahull.setDataStatusCode(DataStatus.STATUS_OK);
            datahull.setOriginData(data);
            parseDataForDataHull(datahull);
            return true;
        } else {
            return false;
        }
    }
    
    private void saveToCache(DataHull datahull) {
        HttpCache cache = new HttpCache(mContext, mCacheTime);
        cache.saveCache(datahull.getRequestUrlString(), datahull.getOriginData());
    }

    private boolean parseDataForDataHull(DataHull datahull) {
        if (mDataParser == null) {
            // no DataParser means user does not need to parse data.
            // just return true.
            return true;
        }
        if (datahull.getDataStatusCode() == DataStatus.STATUS_OK) {
            Object data = null;
            try {
                data = mDataParser.parseData(datahull.getOriginData());
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (data == null) {
                datahull.setDataStatusCode(DataStatus.ERR_PARSE_FAIL);
                return false;
            } else {
                datahull.setParsedData(data);
                return true;
            }
        } else {
            return false;
        }
    }

    private static boolean isNetworkAvailable(Context context) {
        boolean netSataus = false;
        ConnectivityManager cwjManager = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = cwjManager.getActiveNetworkInfo();
        if (info != null) {
            netSataus = info.isAvailable() || info.isConnected();
        }
        return netSataus;
    }

    public boolean isCacheEnabled() {
        return mIsNeedCache;
    }

    public long getCacheTime() {
        return mCacheTime;
    }

    public int getTotalRetryCount() {
        return mHttpRetryCount;
    }

    public int getReadTimeOut() {
        return mHttpReadTimeOut;
    }

    public int getConnectTimeOut() {
        return mHttpConnectTimeOut;
    }

    public HashMap<String, String> getHeaders() {
        return mHttpHeaders;
    }

    public void setTaskCallback(TaskCallback callback) {
        mTaskCallback = callback;
    }

    public void setDataParser(DataParser parser) {
        mDataParser = parser;
    }

    public static class Builder {

        private String mUrl;
        private boolean mIsNeedCache = false;
        private long mCacheTime = Constants.DEFAULT_CACHE_TIME;
        private long mHttpRetryInterval = Constants.DEFAULT_RETRY_INTERVAL;
        private int mHttpRetryCount = Constants.DEFAULT_HTTP_RETRY_COUNT;
        private int mHttpReadTimeOut = Constants.DEFAULT_HTTP_READ_TIMEOUT;
        private int mHttpConnectTimeOut = Constants.DEFAULT_HTTP_CONNECT_TIMEOUT;
        private HashMap<String, String> mHttpHeaders;

        private Context mContext;
        private DataParser mDataParser;
        private TaskCallback mTaskCallback;

        public Builder(Context context, String url) {
            mUrl = url;
            mContext = context;
        }

        public BaseRequest create() {
            BaseRequest request = new BaseRequest(
                    mContext, mUrl,
                    mIsNeedCache, mCacheTime,
                    mHttpConnectTimeOut, mHttpReadTimeOut,
                    mHttpRetryCount, mHttpRetryInterval,
                    mHttpHeaders, mDataParser, mTaskCallback);
            return request;
        }

        public Builder setTaskCallback(TaskCallback callback) {
            mTaskCallback = callback;
            return this;
        }

        public Builder setDataParser(DataParser parser) {
            mDataParser = parser;
            return this;
        }

        public Builder setCacheEnabled(boolean needCache) {
            mIsNeedCache = needCache;
            return this;
        }

        public Builder setCacheTime(long cacheTime) {
            mCacheTime = cacheTime > 0 ? cacheTime : Constants.DEFAULT_CACHE_TIME;
            return this;
        }

        public Builder setReadTimeOut(int timeout) {
            mHttpReadTimeOut = timeout > 0 ? timeout : Constants.DEFAULT_HTTP_READ_TIMEOUT;
            return this;
        }

        public Builder setConnectTimeOut(int timeout) {
            mHttpConnectTimeOut = timeout > 0 ? timeout : Constants.DEFAULT_HTTP_CONNECT_TIMEOUT;
            return this;
        }

        public Builder setTotalRetryCount(int retry) {
            mHttpRetryCount = retry >= 0 ? retry : Constants.DEFAULT_HTTP_RETRY_COUNT;
            return this;
        }

        public Builder setRetryInterval(long interval) {
            mHttpRetryInterval = interval > 0 ? interval : Constants.DEFAULT_RETRY_INTERVAL;
            return this;
        }

        public Builder setCookie(String cookie) {
            if (mHttpHeaders == null) {
                mHttpHeaders = new HashMap<String, String>();
            }
            mHttpHeaders.put("Cookie", cookie);
            return this;
        }

        public Builder setHeader(String key, String value) {
            if (mHttpHeaders == null) {
                mHttpHeaders = new HashMap<String, String>();
            }
            mHttpHeaders.put(key, value);
            return this;
        }

        public Builder setHeaders(HashMap<String, String> headers) {
            mHttpHeaders = headers;
            return this;
        }
    }

    public interface DataParser {
        /**
         * BaseRequest will use your parser to parse String into Object
         * @param data
         *      the original String data
         * @return the parsed Object data
         */
        public Object parseData(String data);
    }

    public interface TaskCallback {
        /**
         * callback when the request is done
         * @param datahull
         *      the encapsulated data and status get from the request
         */
        public void onResponse(DataHull datahull);
    }
}
